/**
*
Java Diagram Package; An extremely flexible and fast multipurpose diagram
component for Swing.
Copyright (C) 2001 Eric Crahen <crahen@cse.buffalo.edu>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package util;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* @class AssociativeMap
*
* @date 08-20-2001
* @author Eric Crahen
* @version 1.0
*
* This is a simple Map implementation that works just like a normal
* Map with a few small changes. This Map can have more than one
* mapping per key. It works by using an internal map which maps
* each key to an ArrayList which holds all the mapped values.
* This faster for associating groups of values by key and not
* neccessarily for searching for specifc values.
*/
public class AssociativeMap extends AbstractMap
implements MultiMap, Serializable {
// Hash to each ArrayList
private HashMap keyMap = new HashMap();
// List of all ArrayLists to avoid creaing too many new Iterators
private ArrayList collectionList = new ArrayList();
// Place to copy
private Object[] copyList = new Object[4];
/**
* Removes all mappings from this map
*/
public void clear() {
keyMap.clear();
}
/**
* Returns true if this map maps the key to this value.
*
* @param Object key
* @param Object value
*
* @return boolean
*/
public boolean contains(Object key, Object value) {
if(key == null || value == null)
return false;
ArrayList list = (ArrayList)keyMap.get(key);
return list.contains(value);
}
/**
* Returns true if this map contains a mapping for the specified key.
*
* @param Object key
* @return boolean
*/
public boolean containsKey(Object key) {
return keyMap.containsKey(key);
}
/**
* Returns true if this map maps one or more keys to this value.
*
* @param Object value
* @return boolean
*/
public boolean containsValue(Object value) {
for(int i=0; i < collectionList.size(); i++)
if( ((ArrayList)collectionList.get(i)).contains(value) )
return true;
/*
// Check each list
for(Iterator i = keyMap.values().iterator(); i.hasNext(); ) {
if( ((ArrayList)i.next()).contains(value) )
return true;
}
*/
return false;
}
/**
* Returns a set view of the keys contained in this map.
*
* @return Set
*/
public Set keySet() {
return keyMap.keySet();
}
/**
* Returns the first value to which this map maps the specified key.
*
* @param Object key
* @return Object
*/
public Object get(Object key) {
if(key == null)
throw new IllegalArgumentException();
ArrayList list = (ArrayList)keyMap.get(key);
if(list == null)
return null;
if(list.size() > 0)
return list.get(0);
// Remove an empty list
removeEntryList(key, list);
return null;
}
/**
* Returns true if this map contains no key-value mappings
*
* @return boolean
*/
public boolean isEmpty() {
if(!keyMap.isEmpty()) {
for(int i=0; i < collectionList.size(); i++)
if( ((ArrayList)collectionList.get(i)).size() != 0)
return false;
}
return true;
}
/**
* Associates the specified value with the specified key in this map. Does
* not check for dupiclates. Returns the value added or null
*
* @param Object key
* @param Object value
* @return Object
*/
public Object put(Object key, Object value) {
if(key == null || value == null)
throw new IllegalArgumentException();
return getEntryList(key).add(value) ? value : null;
}
/**
* Get the entry set for the given key.
*/
private final ArrayList getEntryList(Object key) {
ArrayList list = (ArrayList)keyMap.get(key);
if(list == null) {
list = new ArrayList();
keyMap.put(key, list);
collectionList.add(list);
}
return list;
}
/**
* Remove a keys collection from the control structures
*/
private final void removeEntryList(Object key, ArrayList list) {
keyMap.remove(key);
collectionList.remove(list);
list.clear();
}
/**
* Removes the first mapping for this key from this map if present.
*
* @param Object key
* @return Object
*/
public Object remove(Object key) {
// Remove the first entry in the list
ArrayList list = (ArrayList)keyMap.get(key);
Object value;
if(list == null || ((value = list.remove(0)) == null))
return null;
// If the list is empty un-map it
if(list.size() == 0)
removeEntryList(key, list);
return value;
}
/**
* Removes a particular mapping, or all mappings to the given
* value if the key is null.
*
* @param Object key or all keys if null
* @param Object value
* @return
*/
public boolean remove(Object key, Object value) {
if(key == null)
removeAllValues(value);
else { // Remove one mapping
ArrayList list = (ArrayList)keyMap.get(key);
int index;
if(list != null && ((index = list.indexOf(value)) >= 0)) {
// Remove that item
list.remove(index);
// If the list is empty un-map it
if(list.size() == 0)
removeEntryList(key, list);
}
}
return true;
}
private final void removeAllValues(Object value) {
// Remove the value from all lists it may appear in
int index;
for(int i=0; i < collectionList.size(); i++) {
ArrayList list = (ArrayList)collectionList.get(i);
if((index = list.indexOf(value)) > -1)
list.remove(index);
}
}
/**
* Removes the all mappings for this key from this map
*
* @param Object key
*/
public void removeAll(Object key) {
// Remove the first entry in the list
ArrayList list = (ArrayList)keyMap.get(key);
if(list != null)
removeEntryList(key, list);
}
/**
* Copies all of the mappings from the specified map to this map
*
* @param Map
*/
public void putAll(Map m) {
for(Iterator i = m.entrySet().iterator(); i.hasNext(); ) {
Map.Entry e = (Map.Entry)i.next();
put(e.getKey(), e.getValue());
}
}
/**
* Create all of the mappings from the specified key to the elements in
* the given Collection.
*
* @param Object
* @param Collection
*/
public void putAll(Object key, Collection c) {
for(Iterator i = c.iterator(); i.hasNext(); )
put(key, i.next());
}
/**
* Create all of the mappings from the specified key to the elements in
* the given Collection.
*
* @param Object
* @param Object[]
*/
public void putAll(Object key, Object[] o) {
for(int i=0; i < o.length; i++)
if(o[i] != null)
put(key, o[i]);
}
/**
* Add this item to the collection
*
* @param Object
*/
public void put(Object value) {
put(keyMap, value);
}
/**
* Get all the values mapped to the given key
*
* @param Object key
* @param Object[] avoid extra allocation
*/
public Object[] getAll(Object key, Object[] array) {
if(key == null) { // Copy all
Class t = (array == null) ? Object.class : array.getClass().getComponentType();
int sz = size();
if(array.length < sz)
array = (Object[])java.lang.reflect.Array.newInstance(t, sz);
// Copy each sublist to a temporary location, then append to
// the target list
for(int x = 0, i=0; i < collectionList.size(); i++) {
// copy
ArrayList list = (ArrayList)collectionList.get(i);
copyList = list.toArray(copyList);
// append
int z = list.size();
System.arraycopy(copyList, 0, array, x, z);
x += z;
}
// null terminate
if(array.length > sz)
java.util.Arrays.fill(array, sz, array.length, null);
java.util.Arrays.fill(copyList, null);
return array;
}
// Copy a certain set
ArrayList list = (ArrayList)keyMap.get(key);
return list.toArray(array);
}
/**
* Returns the number of key-value mappings in this map.
*
* @return int
*/
public int size() {
int sz = 0;
for(int i=0; i < collectionList.size(); i++)
sz += ((ArrayList)collectionList.get(i)).size();
/*
// Count each list
for(Iterator i = keyMap.values().iterator(); i.hasNext(); )
sz += ((ArrayList)i.next()).size();
*/
return sz;
}
/**
* Returns a set view of the mappings contained in this map. Using the
* entrySet() is not optimized in any way.
*
* @return Set
*/
public Set entrySet() {
return new AbstractSet() {
public Iterator iterator() {
return new EntryIterator();
}
public int size() {
return AssociativeMap.this.size();
}
};
}
/**
* Returns a collection view of the values contained in this map.
*
* The set returned is read-only. The Collection returned uses a
* single Iterator instance which is reset with each iterator()
* invocation. This makes it convienent to cache an instance of the
* values collection at any point, and simply call iterator() to
* walk over the current values of the MultiMap.
*
* @return Collection
*/
public Collection values() {
return new AbstractCollection() {
ValueIterator iter = new ValueIterator();
public Iterator iterator() {
iter.reset();
return iter;
}
public int size() {
return AssociativeMap.this.size();
}
};
}
/**
* Returns a collection view of the values contained in this map
* which map to a certain key.
*
* The set returned is read-only. The Collection returned uses a
* single Iterator instance which is reset with each iterator()
* invocation. This makes it convienent to cache an instance of the
* values collection at any point, and simply call iterator() to
* walk over the current values of the MultiMap for the requested
* key.
*
* @param Object key
* @return Collection
*/
public Collection values(Object key) {
if(key == null)
throw new IllegalArgumentException();
final Object k = key;
return new AbstractCollection() {
ValueIterator iter = new ValueIterator();
public Iterator iterator() {
iter.reset();
iter.key = k;
return iter;
}
public int size() {
return (iter.list == null) ? 0 : iter.list.size();
}
};
}
/**
* @class ValueIterator
*
* Creates an iterator that will walk over the elements in each
* ArrayList that is mapped by some key.
*/
protected class ValueIterator implements Iterator {
int n = 0, m = 0;
ArrayList list = null;
Object key = null;
/**
* Set the key for this map
*
* @return Object
*/
public void setKey(Object key) {
this.key = key;
}
/**
* Test for the existance of the next element
*
* @return boolean
*/
public boolean hasNext() {
while(list != null || n < collectionList.size()) {
// Valid list?
if(list != null && m < list.size())
return true;
// Get the next list
if(list == null) {
list = (ArrayList)collectionList.get(n++);
m = 0;
// Skip whole lists until a key matches
if( key == null || keyMap.get(key) == list)
return true;
}
list = null;
}
key = null;
return false;
}
/**
* Get the next item in the iteration
*
* @return Object
*/
public Object next() {//System.err.println("K:" + key + " " + list.size() + "/" + m);
return list.get(m++);
}
/**
* Not implemented
*/
public void remove() {
// list.remove(m-1);
}
/**
* Reset the Iterator.
*/
public void reset() {
n = 0;
m = 0;
list = null;
}
/*
Iterator v = keyMap.values().iterator();
Iterator i;
public boolean hasNext() {
while(i == null || !i.hasNext()) {
if(!v.hasNext())
return false;
i = ((ArrayList)v.next()).iterator();
}
return true;
}
public Object next() {
return i.next();
}
public void remove() {
i.remove();
}
*/
}
/**
* @class EntryIterator
*
* Creates an iterator that will walk over the elements in each
* ArrayList that is mapped by some key. It will present them in
* an entrySet (Map.Entry items) view
*/
protected class EntryIterator implements Iterator {
Iterator v = keyMap.entrySet().iterator();
Iterator i;
MultiMap.Entry e = new MultiMap.Entry();
/**
* Test for the existance of the next element
*
* @return boolean
*/
public boolean hasNext() {
while(i == null || !i.hasNext()) {
if(!v.hasNext())
return false;
Map.Entry entry = (Map.Entry)v.next();
ArrayList list = (ArrayList)entry.getValue();
// Ignore keys with empty lists
if(list.size() > 0) {
e.key = entry.getKey();
i = list.iterator();
}
}
return true;
}
/**
* Get the next item in the iteration
*
* @return Object
*/
public Object next() {
e.val = i.next();
return e;
}
/**
* Not implemented
*/
public void remove() { }
/**
* Reset the Iterator.
*/
public void reset() {
v = keyMap.entrySet().iterator();
i = null;
}
}
////////////
public static void main(String[] args) {
MultiMap m = new AssociativeMap();
m.put("A", "1");
m.put("A", "2");
m.put("B", "3");
printAll(m);
// m.remove("A","2");
m.removeAll("A");
printAll(m);
}
protected static void printAll(MultiMap m) {
System.err.println("VALUES:");
for(Iterator i = m.values("A").iterator(); i.hasNext();)
System.err.println(i.next());
System.err.println("ENTRIES:");
for(Iterator i = m.entrySet().iterator(); i.hasNext();)
System.err.println(i.next());
}
}